﻿@using MudBlazor.Docs.Models
@using System.Text.RegularExpressions
@using System.Reflection
@using System.Web
@using Microsoft.Extensions.DependencyInjection
@using System.Globalization
@using MudBlazor.Docs.Extensions
@namespace MudBlazor.Docs.Components



<DocsPage>
    <DocsPageHeader ComponentLink="@ApiLink.GetComponentLinkFor(Type)" Title="@($"{Type.ConvertToCSharpSource()} API")" />
    <DocsPageContent>

        @if (Properties != null && Properties.Count() > 0)
        {
            <DocsPageSection>
                <SectionHeader Title="Properties" />
                <SectionContent Class="docs-content-api" Outlined="false">
                    <EnumSwitch T="Grouping" @bind-Value="@_propertiesGrouping" Color="Color.Primary" />
                    <MudTable Items="@Properties" Elevation="0" Class="ma-0 mud-width-full" Breakpoint="@Breakpoint.Sm" GroupBy="@PropertiesGroupDefinition">
                        <HeaderContent>
                            <MudTh>Name</MudTh>
                            <MudTh>Type</MudTh>
                            <MudTd>Default</MudTd>
                            <MudTh>Description</MudTh>
                        </HeaderContent>
                        <GroupHeaderTemplate>
                            @if (_propertiesGrouping == Grouping.Inheritance && (Type)context.Key != Type)
                            {
                                <MudTh colspan="4">
                                    <MudText Typo="Typo.h6">@($"Inherited from {((Type)context.Key).ConvertToCSharpSource()}")</MudText>
                                </MudTh>
                            }
                            else if (_propertiesGrouping == Grouping.Category)
                            {
                                <MudTh colspan="4">
                                    <MudText Typo="Typo.h6">@context.Key</MudText>
                                </MudTh>
                            }
                        </GroupHeaderTemplate>
                        <RowTemplate>
                            <MudTd Class="docs-content-api-cell" DataLabel="Name">
                                <CodeInline>@context.Name</CodeInline>
                                @if (_propertiesGrouping == Grouping.Inheritance && IsOverridden(context.PropertyInfo))
                                {
                                    <MudChip Variant="Variant.Outlined" Color="Color.Secondary" Size="Size.Small">overridden</MudChip>
                                }
                            </MudTd>
                            <MudTd Class="docs-content-api-cell" DataLabel="Type">
                                <div style="display: flex; flex-direction: row; align-items: center; white-space: nowrap;">
                                    <DocsTypeInfo Type="@context.Type" />
                                    @if (context.IsTwoWay)
                                    {
                                        <MudTooltip Text="Binding TwoWay" Placement="Placement.Top">
                                            <MudIcon Icon="@Icons.Material.Filled.SyncAlt" Color="Color.Default" Class="ml-2 mt-2" />
                                        </MudTooltip>
                                    }
                                </div>
                            </MudTd>
                            <MudTd Class="docs-content-api-cell" DataLabel="Default">
                                @if (PresentDefaultValue(context.Default).Contains("<path"))
                                {
                                    <MudIcon Icon="@PresentDefaultValue(context.Default).Substring(1, PresentDefaultValue(context.Default).Length - 1)" />
                                }
                                else
                                {
                                    <MudText Typo="Typo.body2">@PresentDefaultValue(context.Default)</MudText>
                                }                           
                            </MudTd>
                            <MudTd Class="docs-content-api-cell docs-content-api-description" DataLabel="Description">@(HttpUtility.HtmlDecode(context.Description))</MudTd>
                        </RowTemplate>
                        <GroupFooterTemplate></GroupFooterTemplate>
                    </MudTable>
                </SectionContent>
            </DocsPageSection>
        }

        @if (EventCallbacks != null && EventCallbacks.Count() > 0)
        {
            <DocsPageSection>
                <SectionHeader Title="EventCallbacks" />
                <SectionContent Class="docs-content-api" Outlined="false">
                    <MudTable Items="@EventCallbacks" Elevation="0" Class="ma-0 mud-width-full" Breakpoint="@Breakpoint.Sm">
                        <HeaderContent>
                            <MudTh>Name</MudTh>
                            <MudTh>Type</MudTh>
                            <MudTh>Description</MudTh>
                        </HeaderContent>
                        <RowTemplate>
                            <MudTd Class="docs-content-api-cell" DataLabel="Name"><CodeInline>@context.Name</CodeInline></MudTd>
                            <MudTd Class="docs-content-api-cell" DataLabel="Type"><div style="display: flex; flex-direction: row; align-items: center; white-space: nowrap;">@context.Type.ConvertToCSharpSource()</div></MudTd>
                            <MudTd Class="docs-content-api-cell docs-content-api-description" DataLabel="Description">@(HttpUtility.HtmlDecode(context.Description))</MudTd>
                        </RowTemplate>
                    </MudTable>
                </SectionContent>
            </DocsPageSection>
        }

        @if (Methods != null && Methods.Count() > 0)
        {
            <DocsPageSection>
                <SectionHeader Title="Methods" />
                <SectionContent Class="docs-content-api" Outlined="false">
                    <MudTable Items="@Methods" Elevation="0" Class="ma-0 mud-width-full" Breakpoint="@Breakpoint.Sm">
                        <HeaderContent>
                            <MudTh>Name</MudTh>
                            <MudTd>Parameters</MudTd>
                            <MudTh>Return</MudTh>
                            <MudTh>Description</MudTh>
                        </HeaderContent>
                        <RowTemplate>
                            <MudTd Class="docs-content-api-cell" DataLabel="Name"><CodeInline>@context.Signature</CodeInline></MudTd>
                            <MudTd Class="docs-content-api-cell" DataLabel="Parameters">
                            @if (context.Parameters != null)
                            {
                                foreach (var parameterInfo in context.Parameters)
                                {
                                    <div style="display: flex; flex-direction: row; align-items: center; white-space: nowrap;">@($"{MudBlazor.Docs.Extensions.MethodInfoExtensions.GetAliases(parameterInfo.ParameterType.ConvertToCSharpSource())} {parameterInfo.Name}{AnalyseMethodDocumentation(context.Documentation, "param", parameterInfo.Name)}")</div>
                                }
                            }
                            </MudTd>
                            <MudTd Class="docs-content-api-cell" DataLabel="Return">
                            @if (@context.Return != null && context.Return.ParameterType.ConvertToCSharpSource() != "Void")
                            {
                                <div style="display: flex; flex-direction: row; align-items: center; white-space: nowrap;">@($"{MudBlazor.Docs.Extensions.MethodInfoExtensions.GetAliases(context.Return.ParameterType.ConvertToCSharpSource())}{AnalyseMethodDocumentation(context.Documentation, "returns")}")</div>
                            }
                            </MudTd>
                            <MudTd Class="docs-content-api-cell docs-content-api-description" DataLabel="Description">@(HttpUtility.HtmlDecode(AnalyseMethodDocumentation(context.Documentation, "summary")))</MudTd>
                        </RowTemplate>
                    </MudTable>
                </SectionContent>
            </DocsPageSection>
        }
    </DocsPageContent>
</DocsPage>

<div style="display: none;">
    @RenderTheType()
</div>

@code {
    private List<string> hiddenMethods = new List<string>()
    {
        "ToString",
        "GetType",
        "GetHashCode",
        "Equals",
        "SetParametersAsync",
        "ReferenceEquals"
    };
    [Parameter] public Type Type { get; set; }

    private IEnumerable<ApiProperty> EventCallbacks
    {
        get
        {
            foreach (var info in Type.GetPropertyInfosWithAttribute<ParameterAttribute>().OrderBy(x => x.Name))
            {
                if (info.GetCustomAttributes(typeof(System.ObsoleteAttribute), true).Length == 0 && info.PropertyType.Name.Contains("EventCallback"))
                {
                    yield return new ApiProperty()
                    {
                        Name = info.Name,
                        PropertyInfo = info,
                        Default = string.Empty,
                        Description = DocStrings.GetMemberDescription(Type, info),
                        IsTwoWay = CheckIsTwoWayEventCallback(info),
                        Type = info.PropertyType,
                    };
                }
            }
        }
    }

    private IEnumerable<ApiMethod> Methods
    {
        get
        {
            if (Type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static) == null)
            {
                yield return null;
            }
            else
            {
                foreach (var info in Type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static).OrderBy(x => x.Name))
                {
                    if (!hiddenMethods.Any(x => x.Contains(info.Name)) && !info.Name.StartsWith("get_") && !info.Name.StartsWith("set_"))
                    {
                        if (info.GetCustomAttributes(typeof(System.ObsoleteAttribute), true).Length == 0)
                        {
                            yield return new ApiMethod()
                            {
                                MethodInfo = info,
                                Return = info.ReturnParameter,
                                Signature = info.GetSignature(),
                                Parameters = info.GetParameters(),
                                Documentation = DocStrings.GetMemberDescription(Type, info)
                            };
                        }
                    }
                }
            }
        }
    }

    private IEnumerable<ApiProperty> Properties
    {
        get
        {
            foreach (var info in Type.GetPropertyInfosWithAttribute<ParameterAttribute>()
                                     .OrderBy(x => _propertiesGrouping switch {
                                                        Grouping.Category => x.GetCustomAttribute<CategoryAttribute>()?.Order ?? int.MaxValue - 1,
                                                        Grouping.Inheritance => -NumberOfAncestorClasses(BaseDefinitionClass(x)),
                                                        _ => 0
                                                    })
                                     .ThenBy(x => x.Name))
            {
                if (info.GetCustomAttributes(typeof(System.ObsoleteAttribute), true).Length == 0 && !info.PropertyType.Name.Contains("EventCallback"))
                {
                    yield return new ApiProperty()
                    {
                        Name = info.Name,
                        PropertyInfo = info,
                        Default = GetDefaultValue(info),
                        IsTwoWay = CheckIsTwoWayProperty(info),
                        Description = DocStrings.GetMemberDescription(Type, info),
                        Type = info.PropertyType
                    };
                }
            }
        }
    }


    private string AnalyseMethodDocumentation(string documentation, string occurrence, string parameter = "")
    {
        try
        {
            // Define local variable
            string doublequotes = @"""";

            // Define the start tag and the end tag
            string endTag = $"</{occurrence}>";
            string startTag = $"<{occurrence}{(parameter == string.Empty ? "" : " name=" + doublequotes + parameter + doublequotes)}>";

            // Check if the documentation is valid and contains the start tag
            if (documentation != null && documentation.Contains(startTag))
            {
                // Remove the beginning of the documentation until the start tag
                documentation = documentation.Substring(documentation.IndexOf(startTag), documentation.Length - documentation.IndexOf(startTag));

                // Check if the documentation contains the end tag
                if (documentation.Contains(endTag))
                {
                    // Return the extracted information
                    // If the information is not for summary, ' : ' is only added if there is a non-empty information to be returned
                    return (occurrence != "summary" && documentation.Substring(startTag.Length, documentation.IndexOf(endTag) - startTag.Length).Trim() != "" ? " : " : "") +
                        documentation.Substring(startTag.Length, documentation.IndexOf(endTag) - startTag.Length).Trim();
                }
            }
        }
        catch
        {
            // ignored
        }

        return string.Empty;
    }

    private bool CheckIsTwoWayEventCallback(PropertyInfo propertyInfo) => propertyInfo.Name.EndsWith("Changed") ? true : false;

    private bool CheckIsTwoWayProperty(PropertyInfo propertyInfo)
    {
        PropertyInfo eventCallbackInfo = Type.GetProperty(propertyInfo.Name + "Changed");

        return eventCallbackInfo != null &&
               eventCallbackInfo.PropertyType.Name.Contains("EventCallback") &&
               eventCallbackInfo.GetCustomAttribute<ParameterAttribute>() != null &&
               eventCallbackInfo.GetCustomAttribute<ObsoleteAttribute>() == null;
    }

    // used for default value getting
    object _comp_instance;

    RenderFragment RenderTheType()
    {
        if (!Type.IsAssignableTo(typeof(IComponent)))
            return null;
        return new RenderFragment(builder =>
        {
            builder.OpenComponent(0, Type);
            builder.AddComponentReferenceCapture(1, inst => { _comp_instance = inst; });
            builder.CloseComponent();
        });
    }

    private object GetDefaultValue(PropertyInfo info)
    {
        try
        {
            if (_comp_instance == null)
            {
                return null;
                //RenderTheType();
            }
            return info.GetValue(_comp_instance);
        }
        catch(Exception){}
        return null;
    }

    DefaultConverter<object> _converter = new DefaultConverter<object>() { Culture= CultureInfo.InvariantCulture };

    private string PresentDefaultValue(object @default)
    {
        if (@default == null)
            return "null";
        if (@default.GetType() == typeof(string))
        {
            if (@default.ToString() == string.Empty)
            {
                return "";
            }
            else
            {
                return $"\"{@default}\"";
            }
        }
        if (@default.GetType().IsEnum)
            return $"{@default.GetType().Name}.{@default}";
        if (Nullable.GetUnderlyingType(@default.GetType())!=null)
            return _converter.Set(@default);
        if (@default.GetType().IsGenericType) // for instance event callbacks
            return "";
        if (@default.GetType().IsValueType)
            return _converter.Set( @default);
        return "";
    }


    #region Grouping properties

    private enum Grouping { Category, Inheritance, None }

    private Grouping _propertiesGrouping = Grouping.Inheritance;

    private TableGroupDefinition<ApiProperty> PropertiesGroupDefinition => _propertiesGrouping switch
    {
        Grouping.Category => new() { Selector = (p) => p.PropertyInfo.GetCustomAttribute<CategoryAttribute>()?.Name ?? "Misc" },
        Grouping.Inheritance => new() { Selector = (p) => BaseDefinitionClass(p.PropertyInfo) },
        _ => null
    };

    // -- Grouping properties by inheritance ------------------------------------------------------------------------------------------

    private static Type BaseDefinitionClass(MethodInfo m) => m.GetBaseDefinition().DeclaringType;

    private static Type BaseDefinitionClass(PropertyInfo p) => BaseDefinitionClass(p.GetMethod ?? p.SetMethod);  // used for grouping properties

    private static bool IsOverridden(MethodInfo m) => m.GetBaseDefinition().DeclaringType != m.DeclaringType;

    private static bool IsOverridden(PropertyInfo p) => IsOverridden(p.GetMethod ?? p.SetMethod);                // used for the "overridden" chip

    // used for ordering groups of properties
    private static int NumberOfAncestorClasses(Type type)
    {
        int n = 0;
        while ((type = type.BaseType) != null)
            n++;
        return n;
    }

    #endregion
}
